﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

namespace Heimdallr.Async
{
    /// <summary>
    /// Represents an asynchronous operation which continuously consumes a queue.  
    /// </summary>
    /// <typeparam name="T">Specifies the type of elements in the queue.</typeparam>
    public class QueueConsumer<T>
    {
        #region Field

        private readonly Action<T> action;
        private readonly ConcurrentQueue<T> queue;
        private readonly object thisLock;

        #endregion

        #region Property

        /// <summary>
        /// Gets a value indicating whether the <see cref="QueueConsumer{T}"/> is running an asynchronous operation.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                bool result = Monitor.TryEnter(this.thisLock);

                if (result)
                    Monitor.Exit(this.thisLock);

                return result;
            }
        }

        #endregion

        #region Event

        /// <summary>
        /// Occurs when the queue is empty and asynchronous operation has completed.
        /// </summary>
        public event EventHandler<CompletedEventArgs> Completed;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class, using specified queue-consuming action.
        /// </summary>
        /// <param name="action">An action which will be called once a new item enqueued.</param>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
        public QueueConsumer(Action<T> action)
            : this(action, new T[] { })
        {
        }

        /// <summary>
        /// Initializes a new instance of this class, using specified queue-consuming action and exists items.
        /// </summary>
        /// <param name="action">An action which will be called once a new item enqueued.</param>
        /// <param name="collection">Exists items.</param>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> or <paramref name="collection"/> is null.</exception>
        public QueueConsumer(Action<T> action, IEnumerable<T> collection)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (collection == null)
                throw new ArgumentNullException("collection");

            this.action = action;
            this.queue = new ConcurrentQueue<T>(collection);
            this.thisLock = new object();
        }

        #endregion

        #region Method

        /// <summary>
        /// Enqueues an item and starts the asynchronous queue-consuming operation. 
        /// </summary>
        /// <param name="item">An new item.</param>
        public void EnqueueAndStart(T item)
        {
            this.EnqueueAndStart(item, null);
        }

        /// <summary>
        /// Enqueues an item and starts the asynchronous queue-consuming operation. 
        /// </summary>
        /// <param name="item">An new item.</param>
        /// <param name="state">The optional user-supplied state object passed to the <see cref="CompletedEventArgs.UserState"/> property.</param>
        public void EnqueueAndStart(T item, object state)
        {
            this.queue.Enqueue(item);

            if (Monitor.TryEnter(this.thisLock))
            {
                Monitor.Exit(this.thisLock);
                ThreadPool.QueueUserWorkItem(this.ThreadCallback, state);
            }
        }

        #endregion

        #region Callback

        private void ThreadCallback(object state)
        {
            T item;

            lock (this.thisLock)
                while (this.queue.TryDequeue(out item))
                    this.action(item);

            this.OnCompleted(new CompletedEventArgs(state));
        }

        #endregion

        #region Raise Event

        /// <summary>
        /// Raises the <see cref="Completed"/> event. 
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnCompleted(CompletedEventArgs e)
        {
            if (this.Completed != null)
                this.Completed(this, e);
        }

        #endregion
    }

    /// <summary>
    /// Represents an asynchronous operation which continuously consumes a queue.  
    /// </summary>
    /// <typeparam name="TItem">Specifies the type of elements in the queue.</typeparam>
    /// <typeparam name="TState">Specifies the type of user-supplied state.</typeparam>
    public class QueueConsumer<TItem, TState>
    {
        #region Field

        private readonly Action<TItem, TState> action;
        private readonly ConcurrentQueue<TItem> queue;
        private readonly object thisLock;

        #endregion

        #region Property

        /// <summary>
        /// Gets a value indicating whether the <see cref="QueueConsumer{T}"/> is running an asynchronous operation.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                bool result = Monitor.TryEnter(this.thisLock);

                if (result)
                    Monitor.Exit(this.thisLock);

                return result;
            }
        }

        #endregion

        #region Event

        /// <summary>
        /// Occurs when the queue is empty and asynchronous operation has completed.
        /// </summary>
        public event CompletedEventHandler<TState> Completed;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class, using specified queue-consuming action.
        /// </summary>
        /// <param name="action">An action which will be called once a new item enqueued.</param>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
        public QueueConsumer(Action<TItem, TState> action)
            : this(action, new TItem[] { })
        {
        }

        /// <summary>
        /// Initializes a new instance of this class, using specified queue-consuming action and exists items.
        /// </summary>
        /// <param name="action">An action which will be called once a new item enqueued.</param>
        /// <param name="collection">Exists items.</param>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> or <paramref name="collection"/> is null.</exception>
        public QueueConsumer(Action<TItem, TState> action, IEnumerable<TItem> collection)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (collection == null)
                throw new ArgumentNullException("collection");

            this.action = action;
            this.queue = new ConcurrentQueue<TItem>(collection);
            this.thisLock = new object();
        }

        #endregion

        #region Method

        /// <summary>
        /// Enqueues an item and starts the asynchronous queue-consuming operation. 
        /// </summary>
        /// <param name="item">An new item.</param>
        public void EnqueueAndStart(TItem item)
        {
            this.EnqueueAndStart(item, default(TState));
        }

        /// <summary>
        /// Enqueues an item and starts the asynchronous queue-consuming operation. 
        /// </summary>
        /// <param name="item">An new item.</param>
        /// <param name="state">The optional user-supplied state object passed to the <see cref="CompletedEventArgs.UserState"/> property.</param>
        public void EnqueueAndStart(TItem item, TState state)
        {
            this.queue.Enqueue(item);

            if (Monitor.TryEnter(this.thisLock))
            {
                Monitor.Exit(this.thisLock);
                ThreadPool.QueueUserWorkItem(this.ThreadCallback, state);
            }
        }

        #endregion

        #region Callback

        private void ThreadCallback(object state)
        {
            TItem item;
            TState s = (TState)state;

            lock (this.thisLock)
                while (this.queue.TryDequeue(out item))
                    this.action(item, s);

            this.OnCompleted(CompletedEventArgs.Create(s));
        }

        #endregion

        #region Raise Event

        /// <summary>
        /// Raises the <see cref="Completed"/> event. 
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnCompleted(CompletedEventArgs<TState> e)
        {
            if (this.Completed != null)
                this.Completed(this, e);
        }

        #endregion
    }
}
